﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using Red.Controls.Shell;
using Red.Core;
using Red.Launcher.Diagnostics.Process;
using Red.Launcher.Utilities;

namespace Red.Launcher.Dialogs
{
    public class ProcessWatcher : ViewModel
    {
        #region Private Data

        private Process _process;

        #endregion Private Data

        #region Public Properties

        public Process Process
        {
            get { return _process; }
            set
            {
                if ( _process != null )
                {
                    _process.Exited -= _process_Exited;
                }

                _process = value;

                if ( _process != null )
                {
                    _process.Exited += _process_Exited;
                }
            }
        }

        public bool HasExited => Process.HasExited;

        #endregion Public Properties

        #region Event Handlers

        private void _process_Exited( object sender, EventArgs e )
        {
            OnPropertyChanged( "HasExited" );
        }

        #endregion Event Handlers
    }

    public partial class WaitForProcessShutdownDialog : RedWindow
    {
        #region Dependency Properties

        public static readonly DependencyProperty MessageProperty = DependencyProperty.Register( "Message", typeof( string ), typeof( WaitForProcessShutdownDialog ) );

        #endregion Dependency Properties

        #region Public Properties

        public static readonly RoutedUICommand RaiseProcess = new RoutedUICommand( "Raise", "RaiseProcess", typeof( WaitForProcessShutdownDialog ) );
        public static readonly RoutedUICommand CloseProcess = new RoutedUICommand( "Close", "CloseProcess", typeof( WaitForProcessShutdownDialog ) );
        public static readonly RoutedUICommand ForceCloseAll = new RoutedUICommand( "Force Close All", "ForceClose", typeof( WaitForProcessShutdownDialog ) );
        public static readonly RoutedUICommand Cancel = new RoutedUICommand( "Cancel", "Cancel", typeof( WaitForProcessShutdownDialog ) );

        public string Message
        {
            get { return (string)GetValue( MessageProperty ); }
            set { SetValue( MessageProperty, value ); }
        }

        public ObservableCollection<ProcessWatcher> Processes { get; private set; } = new ObservableCollection<ProcessWatcher>();
        public ObservableCollection<Monitor> Monitors { get; private set; } = new ObservableCollection<Monitor>();
        public int ProcessesShutdownCount { get; private set; }
        private DispatcherTimer ProcessPollingTimer { get; set; } = new DispatcherTimer();

        #endregion Public Properties

        #region C-Tor

        partial void InitializeAnalytics();

        public WaitForProcessShutdownDialog()
        {
            InitializeComponent();

            Monitors.CollectionChanged += Monitors_CollectionChanged;
            Processes.CollectionChanged += Processes_CollectionChanged;
            Closed += WaitForProcessShutdownDialog_Closed;
            Loaded += ( object sender, RoutedEventArgs e ) => { CheckForCompletion(); };

            SetupProcessPolling();

            CommandBinding raiseProcessBinding = new CommandBinding( RaiseProcess, RaiseProcess_Executed );
            CommandBindings.Add( raiseProcessBinding );

            CommandBinding closeProcessBinding = new CommandBinding( CloseProcess, CloseProcess_Executed );
            CommandBindings.Add( closeProcessBinding );

            CommandBinding forceCloseBinding = new CommandBinding( ForceCloseAll, ForceCloseAll_Executed );
            CommandBindings.Add( forceCloseBinding );

            CommandBinding cancelBinding = new CommandBinding( Cancel, Cancel_Executed );
            CommandBindings.Add( cancelBinding );

            InitializeAnalytics();
        }

        #endregion C-Tor

        #region Private Methods

        private void SetupProcessPolling()
        {
            ProcessPollingTimer.Tick += ProcessPollingTimer_Tick;
            ProcessPollingTimer.Interval = TimeSpan.FromSeconds( 2 );
            ProcessPollingTimer.Start();
        }

        private void StopProcessPolling()
        {
            ProcessPollingTimer.Stop();
            ProcessPollingTimer.Tick -= ProcessPollingTimer_Tick;
        }

        private void AddProcesses( IEnumerable<Process> processes )
        {
            foreach ( Process process in processes )
            {
                Processes.Add( new ProcessWatcher() { Process = process } );
            }
        }

        private void AddProcesses( Monitor monitor )
        {
            AddProcesses( monitor.Primary );
            AddProcesses( monitor.Children );
        }

        private void CheckForCompletion()
        {
            if ( ProcessesShutdownCount == Processes.Count )
            {
                Debug.Print( "WaitForProcessShutdownDialog Complete" );

                if ( IsActive )
                {
                    DialogResult = true;
                    Close();
                }
            }
        }

        private void ShutdownProcessWatcher( ProcessWatcher watcher )
        {
            watcher.Process.Exited -= Process_Exited;
            watcher.Process = null;
        }

        private void ClearProcesses()
        {
            foreach ( ProcessWatcher watcher in Processes )
            {
                ShutdownProcessWatcher( watcher );
            }

            Processes.Clear();
        }

        private void ClearMonitors()
        {
            foreach ( Monitor monitor in Monitors )
            {
                monitor.Primary.CollectionChanged -= MonitoredProcess_Changed;
                monitor.Children.CollectionChanged -= MonitoredProcess_Changed;
            }

            Monitors.Clear();
        }

        #endregion Private Methods

        #region Event Handlers

        private void WaitForProcessShutdownDialog_Closed( object sender, EventArgs e )
        {
            StopProcessPolling();
            ClearMonitors();
            ClearProcesses();
        }

        private async void ProcessPollingTimer_Tick( object sender, EventArgs e )
        {
            foreach ( Monitor monitor in Monitors )
            {
                await monitor.Refresh();
            }
        }

        private void MonitoredProcess_Changed( object sender, NotifyCollectionChangedEventArgs e )
        {
            if ( e.Action == NotifyCollectionChangedAction.Add )
            {
                foreach ( Process process in e.NewItems )
                {
                    Processes.Add( new ProcessWatcher() { Process = process } );
                }
            }
        }

        private void Monitors_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
        {
            if ( e.Action == NotifyCollectionChangedAction.Add )
            {
                foreach ( Monitor monitor in e.NewItems )
                {
                    AddProcesses( monitor );

                    monitor.Primary.CollectionChanged += MonitoredProcess_Changed;
                    monitor.Children.CollectionChanged += MonitoredProcess_Changed;
                }
            }
            else if ( e.Action == NotifyCollectionChangedAction.Remove )
            {
                foreach ( Monitor monitor in e.OldItems )
                {
                    monitor.Primary.CollectionChanged -= MonitoredProcess_Changed;
                    monitor.Children.CollectionChanged -= MonitoredProcess_Changed;
                }
            }
        }

        private void Processes_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
        {
            if ( e.Action == NotifyCollectionChangedAction.Add )
            {
                foreach ( ProcessWatcher watcher in e.NewItems )
                {
                    watcher.Process.Exited += Process_Exited;
                    if ( watcher.HasExited )
                    {
                        ++ProcessesShutdownCount;
                        CheckForCompletion();
                    }
                }
            }
            else if ( e.Action == NotifyCollectionChangedAction.Remove )
            {
                foreach ( ProcessWatcher watcher in e.OldItems )
                {
                    ShutdownProcessWatcher( watcher );
                }
            }
        }

        private void Process_Exited( object sender, EventArgs e )
        {
            Action method = () =>
            {
                ++ProcessesShutdownCount;
                CheckForCompletion();
            };

            Dispatcher.BeginInvoke( method );
        }

        private void RaiseProcess_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            ProcessWatcher processWatcher = (ProcessWatcher)( (FrameworkElement)e.OriginalSource ).DataContext;
            processWatcher.Process.BringToForeground();
        }

        private void CloseProcess_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            ProcessWatcher processWatcher = (ProcessWatcher)( (FrameworkElement)e.OriginalSource ).DataContext;
            processWatcher.Process.CloseMainWindow();
        }

        private void ForceCloseAll_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            ProcessWatcher processWatcher = (ProcessWatcher)( (FrameworkElement)e.OriginalSource ).DataContext;

            foreach ( ProcessWatcher watcher in Processes )
            {
                if ( !watcher.HasExited )
                {
                    watcher.Process.Kill();
                }
            }
        }

        private void Cancel_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            Debug.Print( "WaitForProcessShutdownDialog Cancelled" );

            DialogResult = false;
            Close();
        }

        #endregion Event Handlers
    }
}
